import 'dart:math' as math;
import 'package:flutter/painting.dart';
import 'package:flutter/widgets.dart';

/// [TextLayout]：用于将局部坐标映射到文档中的绝对 offset（字符位置）。
///
/// 功能：
/// - 缓存逐行的 [TextPainter]，避免在拖拽/命中测试时频繁创建对象；
/// - 支持通过 [setLines] 更新当前文本内容（文本变化时需调用）；
/// - 假设各行由 `\n` 分隔，并在内部维护每一行在整段文本中的起始绝对偏移；
/// - 通过 [offsetToPosition] 将局部坐标 + 滚动偏移量转换为文档中的绝对位置。
///
/// 使用示例：
/// ```dart
/// final layout = TextLayout(
///   textStyle: const TextStyle(fontSize: 16, fontFamily: 'monospace'),
///   lineHeight: kEditorLineHeight,
///   padding: kEditorPadding,
///   lineNumberWidth: kEditorLineNumberWidth,
/// );
///
/// layout.setLines(buffer.getLines());
/// final pos = layout.offsetToPosition(local, scrollY);
/// ```
class TextLayout {
  /// 创建一个 [TextLayout] 实例。
  ///
  /// - [textStyle] 文本的基础样式（必须与渲染一致）；
  /// - [lineHeight] 行高（必须与编辑器一致，用于行号计算）；
  /// - [padding] 内边距；
  /// - [lineNumberWidth] 行号区域宽度（用于修正 X 坐标）。
  TextLayout({
    required this.textStyle,
    required this.lineHeight,
    required this.padding,
    required this.lineNumberWidth,
  });

  /// 文本样式（与渲染一致，用于构造 [TextPainter]）。
  final TextStyle textStyle;

  /// 每行高度。
  final double lineHeight;

  /// 顶部/左侧内边距。
  final double padding;

  /// 行号区域的宽度。
  final double lineNumberWidth;

  List<String> _lines = const [];

  /// 每行在整段文本中的起始绝对偏移。
  /// （包含前面行长度 + 1 个换行符的累积结果）
  List<int> _lineStartOffsets = const [];

  /// 每行对应的 [TextPainter] 缓存。
  /// 用于避免频繁创建对象。
  List<TextPainter?> _painters = const [];

  /// 文本总长度的估算值。
  /// 用于裁剪最终计算得到的 offset。
  int _totalLengthEstimate = 0;

  /// 更新当前用于命中测试的多行文本内容。
  ///
  /// - 需要在文本变化时调用；
  /// - 会重建 `_lines`、`_lineStartOffsets` 与 `_painters` 缓存；
  /// - 每行末尾都会假设存在一个 `\n`（与原实现保持一致）。
  void setLines(List<String> lines) {
    _lines = List<String>.from(lines);
    _painters = List<TextPainter?>.filled(_lines.length, null, growable: false);

    _lineStartOffsets = List<int>.filled(_lines.length, 0, growable: false);
    int pos = 0;
    for (int i = 0; i < _lines.length; i++) {
      _lineStartOffsets[i] = pos;
      pos += _lines[i].length + 1; // 每行末尾假设有一个 '\n'
    }
    _totalLengthEstimate =
    _lines.isEmpty ? 0 : _lineStartOffsets.last + _lines.last.length;
  }

  /// 当前行数。
  int get lineCount => _lines.length;

  /// 将 [local] 坐标（相对编辑区域）与 [scrollY]（滚动值）映射到文档绝对 offset。
  ///
  /// - 若文档为空，返回 `0`；
  /// - 会根据 Y 坐标推算行号，再根据 [TextPainter] 推算列号；
  /// - 最终结果为 `行起点 offset + 列号`；
  /// - 会自动裁剪到 `[0, _totalLengthEstimate]` 范围。
  int offsetToPosition(Offset local, double scrollY) {
    if (_lines.isEmpty) return 0;

    // 根据 Y 坐标计算所在行
    final double y = local.dy + scrollY - padding;
    final int row = ((y) / lineHeight)
        .floor()
        .clamp(0, math.max(0, _lines.length - 1));

    final String line = _lines[row];
    final tp = _ensurePainter(row);

    // 修正 X 坐标，避免落在行号区域/负值
    final double caretDx = math.max(0.0, local.dx - lineNumberWidth - padding);
    final pos = tp.getPositionForOffset(Offset(caretDx, 0));
    final int col = pos.offset.clamp(0, line.length);

    final int absPos = _lineStartOffsets[row] + col;
    return absPos.clamp(0, _totalLengthEstimate);
  }

  /// 确保指定行号有对应的 [TextPainter]，若无则构建并缓存。
  TextPainter _ensurePainter(int row) {
    final cached = _painters[row];
    if (cached != null) return cached;

    final painter = TextPainter(
      text: TextSpan(text: _lines[row], style: textStyle),
      textDirection: TextDirection.ltr,
      maxLines: 1,
    )..layout();
    _painters[row] = painter;
    return painter;
  }
}
